/**
 * Description:
 * Class Name:NServer
 * Date:2022/5/24 14:53
 *
 * @author LTisme
 * @version JAVA17
 */
package 疯狂Java讲义.第17章_网络编程._17_3_基于TCP协议的网络编程._17_3_7_使用NIO实现非阻塞Socket通信;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;

public class NServer {
    // 用于检测所有Channel状态的Selector
    private Selector selector = null;
    static final int PORT = 30000;
    static final String targetIP = "localhost";
    // 定义实现编码、解码的字符集对象
    private Charset charset = Charset.forName("UTF-8");
    public void init() throws IOException{
        // 用Selector的open()静态方法创建Selector实例
        selector = Selector.open();
        // 通过open方法来打开一个未绑定的ServerSocketChannel实例
        ServerSocketChannel server = ServerSocketChannel.open();
        var isa = new InetSocketAddress(targetIP, PORT);
        // 将该ServerSocketChannel绑定到指定的IP地址
        server.bind(isa);
        // 还要将ServerSocket设置为以非阻塞方式工作
        server.configureBlocking(false);
        // 将server注册到指定的Selector对象
        server.register(selector, SelectionKey.OP_ACCEPT);
        while (selector.select() > 0){
            // 依次处理selector上的每个已选择的SelectionKey
            for (SelectionKey sk :
                    selector.selectedKeys()) {
                // 从selector上的已选择Key集中删除正在处理的SelectionKey
                selector.selectedKeys().remove(sk);                                     // ①
                // 如果sk对应的Channel包含客户端的连接请求
                if (sk.isAcceptable()){                                                 // ②
                    // 调用accept方法接受连接，产生服务器端的SocketChannel
                    SocketChannel sc = server.accept();
                    // 设置采用非阻塞模式
                    sc.configureBlocking(false);
                    // 将该SocketChannel也注册到selector
                    sc.register(selector, SelectionKey.OP_READ);
                    // 将sk对应的Channel设置成准备接受其他请求
                    sk.interestOps(SelectionKey.OP_ACCEPT);
                }
                // 如果sk对应的Channel有数据需要读取
                if (sk.isReadable()){                                                   // ③
                    // 获取该SelectionKey对应的Channel，该Channel中有可读的数据
                    var sc = (SocketChannel) sk.channel();
                    // 定义准备执行读取数据的ByteBuffer
                    ByteBuffer buff = ByteBuffer.allocate(1024);
                    var content = "";
                    // 开始读取数据
                    try {
                        while (sc.read(buff) > 0){
                            buff.flip();
                            content += charset.decode(buff);
                        }
                        // 打印从该sk对应的Channel里读取到的数据
                        System.out.println("读取的数据：" + content);
                        // 将sk对应的Channel设置成准备下一次读取
                        sk.interestOps(SelectionKey.OP_READ);
                    }
                    // 如果捕获到该sk对应的Channel出现了异常，即表明该Channel对应的Client出现了问题
                    // 所以从Selector中取消sk的注册
                    catch (IOException exception){
                        // 从Selector中删除指定的SelectionKey
                        sk.cancel();
                        if (sk.channel() != null){
                            sk.channel().close();
                        }
                    }
                    // 如果content的长度大于0，即聊天信息不为空
                    if (content.length() > 0){
                        // 遍历该selector里注册的所有SelectionKey
                        for (SelectionKey key :
                                selector.keys()) {
                            // 获取该key对应的Channel
                            Channel targetChannel = key.channel();
                            // 如果该Channel是SocketChannel对象
                            if (targetChannel instanceof SocketChannel){
                                // 将读到的内容写入该Channel中
                                var dest = (SocketChannel) targetChannel;
                                dest.write(charset.encode(content));
                            }
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws IOException{
        new NServer().init();
    }
}
